﻿// Copyright (c) Vitaly Kamiansky. All rights reserved. 
// See License.txt in the project root.

using Property.Reloaders;
using Property.Sequences;
using System;
using System.Reactive.Linq;

namespace Property
{
    /// <summary>
    /// Contains words that extend the description 
    /// of the sequence-based property value loading process.
    /// </summary>
    public static class SequenceConveyorExtensionsReactive
    {
        /// <summary>
        /// ... with the specified modification ...
        /// </summary>
        /// <param name="modification">Tell me how to modify the value producing sequence.</param>
        /// <param name="testMode">Tell me if I have to ignore this word.</param>
        public static ISequenceConveyor<T> Modify<T>(this ISequenceConveyor<T> source, Func<IObservable<T>, IObservable<T>> modification, bool testMode = false)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (modification == null) throw new ArgumentNullException("modification");

            return testMode ? source : new DelegateSequenceConveyorDecorator<T>(source, modification);
        }

        /// <summary>
        /// ... throwing an exception if getting a new value takes longer than the specified time ...
        /// </summary>
        /// <param name="dueTime">Tell me how long I should wait for the value to come.</param>
        /// <param name="testMode">Tell me if I have to ignore this word.</param>
        public static ISequenceConveyor<T> Timeout<T>(this ISequenceConveyor<T> source, TimeSpan dueTime, bool testMode = false)
        {
            return source.Modify(o => o.Timeout(dueTime), testMode);
        }

        /// <summary>
        /// ... diverting control to the specified handler if any exceptions are thrown ...
        /// </summary>
        /// <param name="handler">Tell me what to do if an error occurs.</param>
        /// <param name="testMode">Tell me if I have to ignore this word.</param>
        public static ISequenceConveyor<T> Catch<T>(this ISequenceConveyor<T> source, Action<Exception> handler, bool testMode = false)
        {
            return source.Modify(o => o
                        .Do(obj => { }, ex => { handler.Invoke(ex); }, () => { })
                        .Catch(Observable.Empty<T>()), testMode);
        }

        /// <summary>
        /// ... trying the specified number of times before throwing an exception ...
        /// </summary>
        /// <param name="retryCount">Tell me how many tries you give me to to get a value.</param>
        /// <param name="testMode">Tell me if I have to ignore this word.</param>
        public static ISequenceConveyor<T> Retry<T>(this ISequenceConveyor<T> source, int retryCount, bool testMode = false)
        {
            return source.Modify(o => o.Retry(retryCount), testMode);
        }

        /// <summary>
        /// <para>... (each time) ...</para>
        /// <para>( ! ) Don't forget to dispose of what you create with this word.</para>
        /// </summary>
        public static IReloaderPrototype<T> Each<T>(this ISequenceConveyor<T> source)
        {
            if (source == null) throw new ArgumentNullException("source");

            return new ReactiveReloaderPrototype<T>(source);
        }

        /// <summary>
        /// <para>... first time ...</para>
        /// <para>( ! ) Don't forget to dispose of what you create with this word.</para>
        /// </summary>
        public static IReloaderPrototype<T> First<T>(this ISequenceConveyor<T> source)
        {
            return source.Each().First();
        }

        /// <summary>
        /// <para>... every odd time ...</para>
        /// <para>( ! ) Don't forget to dispose of what you create with this word.</para>
        /// </summary>
        public static IReloaderPrototype<T> Odd<T>(this ISequenceConveyor<T> source)
        {
            return source.Each().Odd();
        }

        /// <summary>
        /// <para>... if the specified condition is met (when) ...</para>
        /// <para>( ! ) Don't forget to dispose of what you create with this word.</para>
        /// </summary>
        /// <param name="condition">Tell me how to decide if the reload should happen.</param>
        public static IReloaderPrototype<T> Conditional<T>(this ISequenceConveyor<T> source, Func<bool> condition)
        {
            return source.Each().Conditional(condition);
        }

        /// <summary>
        /// <para>... if the discriminator state meets the specified condition (when) ...</para>
        /// <para>( ! ) Don't forget to dispose of what you create with this word.</para>
        /// </summary>
        /// <param name="getState">Tell me how the state should be produced 
        /// based on its previous value.</param>
        /// <param name="condition">Tell me how to use the state to decide if 
        /// the reload should happen.</param>
        public static IReloaderPrototype<T> Conditional<T, TState>(this ISequenceConveyor<T> source, Func<TState, TState> getState, Func<TState, bool> condition)
        {
            return source.Each().Conditional(getState, condition);
        }

        /// <summary>
        /// <para>... if the call passes through the specified filter (when) ...</para>
        /// <para>( ! ) Don't forget to dispose of what you create with this word.</para>
        /// </summary>
        /// <param name="filter">Tell me how to transform the sequence of reload calls.</param>
        /// <param name="testMode">Tell me if I have to ignore this word.</param>
        public static IReloaderPrototype<T> Filtered<T>(this  ISequenceConveyor<T> source, Func<IObservable<object>, IObservable<object>> filter, bool testMode = false)
        {
            return source.Each().Filtered(filter, testMode);
        }

        /// <summary>
        /// <para>... if the discriminator state passes through the specified sequence filter (when) ...</para>
        /// <para>( ! ) Don't forget to dispose of what you create with this word.</para>
        /// </summary>
        /// <param name="getState">Tell me how the state should be produced 
        /// based on its previous value.</param>
        /// <param name="filter">Tell me how to transform the sequence of reload calls 
        /// represented by the state values.</param>
        /// <param name="testMode">Tell me if I have to ignore this word.</param>
        public static IReloaderPrototype<T> Filtered<T, TState>(this  ISequenceConveyor<T> source, Func<TState, TState> getState, Func<IObservable<TState>, IObservable<TState>> filter, bool testMode = false)
        {
            return source.Each().Filtered(getState, filter, testMode);
        }
    }
}
